;
; This file contains some macro definitions.
;
            include  utils.mac
            .xlist
.xcref ?ASET,?AVAL,?PUSH,?POP,?TOS,?J,?JNF,?LBL,?mx
.xcref ?if,?while,?forever
.xcref ?brkcnt,?brklab
.xcref ?dbsp,?STK0,?STKT0,?STKTOPT,?STKTOP,?CTR
?mx MACRO a,b,c,d,e,f ;; Macro to allow multi statements on line
 a
 b
 c
 d
 e
 f
ENDM
?mx <?if equ 1>,<?while equ 2>,<?forever equ 3> ; Declare stack types
?repeat equ 4
?mx ?brkcnt=-1,?brklab=-1                       ; Useful variables for BREAK
?mx IF2,<.ERRNZ ?dbsp ;; Nesting error>,ENDIF     ; Check for nesting errors:
; Initialise variables for stack handling:
?mx ?dbsp=0,?STK0=0,?STKT0=0,?STKTOP=-1,?STKTOPT=-1,?CTR=0

?ASET MACRO v,l,n
 .xcref v&l
 v&l = n ;; Set an 'array' variable.
ENDM

?AVAL MACRO n,v,l
 n = v&l ;; Read an 'array' variable.
ENDM

?PUSH MACRO n,t ;; Push new value onto stack.
?mx ?dbsp=?dbsp+1,?STKTOP=n,?STKTOPT=t
 ?ASET ?STK,%?dbsp,n
 ?ASET ?STKT,%?dbsp,t
ENDM

?POP MACRO ;; Pop top element off stack.
?mx <IF ?dbsp GT 0>,?dbsp=?dbsp-1,ENDIF
   ?AVAL    ?STKTOP,?STK,%?dbsp
   ?AVAL    ?STKTOPT,?STKT,%?dbsp
ENDM

?TOS  MACRO t        ;; Check type of top of stack.
.ERRE ?dbsp ; Stack underflow
.ERRNZ ?STKTOPT NE t ; Stack type mismatch
ENDM

?JNF MACRO c,t,n ;; Produce far conditional jump - beware too many n's.
 local L
.xcref L
            j&c L
            jmp t&n
L LABEL NEAR
ENDM

?J MACRO c,t,n  ;; Jump conditional to t&n
            j&c t&n
ENDM

?LBL  MACRO t,n   ;; Label t&n
   .xcref   t&n
   IFNDEF   t&n
      t&n LABEL NEAR
   ELSE
      IF t&n EQ $
t&n LABEL NEAR
      ENDIF
   ENDIF
ENDM

;
; Now I have some stack handling here are the REAL macros.
;
.IF MACRO f,x,y
   IFNB <f>
    IFDIF <f>,<far>
     IFDIF <f>,<FAR>
      IFDIF <f>,<Far>
       .ERRNB <y> ;; Too many args to .IF
       .IF <>,<f>,<x>
       EXITM
      ENDIF
     ENDIF
    ENDIF
   ENDIF
   ?PUSH ?CTR,?if
   ?CTR=?CTR+1
   IFB   <y>   ;; Only 1 parm - must be condition.
      IFB <f>
         ?J n&x,?E,%?STKTOP
      ELSE ;; Far jump on not x
         ?JNF <x>,?E,%?STKTOP
      ENDIF
   ELSE
   ;; Expand x into a list of instructions:
?mx x
      IFB <f>
         ?J n&y,?E,%?STKTOP
      ELSE
         ?JNF <y>,?E,%?STKTOP
      ENDIF
   ENDIF
ENDM
.ELSE MACRO    ;; Doesn't need a far option.
   ?TOS  ?if
   ?J    <>,?F,%?STKTOP
   ?LBL  ?E,%?STKTOP
ENDM
.FI   MACRO
   ?TOS  ?if
   ?LBL  ?E,%?STKTOP
   ?LBL  ?F,%?STKTOP
   ?POP
ENDM

; All loops have a repeat label ?R... at the top and a break ?B... at the foot

.FOREVER MACRO d   ;; Infinite loop - ignores parameter
   .ERRDIF <d>,<DO>  ; Keyword DO expected
   ?PUSH ?CTR,?forever
   ?CTR = ?CTR+1
   ?LBL  ?R,%?STKTOP
ENDM

.REPEAT MACRO	    ;; Condition at bottom - no parameters here.
   ?PUSH ?CTR,?repeat
   ?CTR = ?CTR+1
   ?LBL  ?R,%?STKTOP
ENDM

; .UNTIL cc  is just: BREAK Ncc inside an infinite loop:
.UNTIL MACRO x
   .ERRB <x> ; Condition expected .UNTIL

   IF ?STKTOPT EQ ?repeat
      IFB <f>
         ?J n&x,?R,%?STKTOP
      ELSE
         ?JNF x,?R,%?STKTOP
      ENDIF
      ?LBL ?B,%?STKTOP
      ?POP
   ELSE
      .ERR Unmatched .UNTIL
   ENDIF
ENDM

; WHILE cc DO is just: BREAK Ncc inside an infinite loop:
.WHILE MACRO f,x,y,z
   IFNB <f>
    IFDIF <f>,<far>
     IFDIF <f>,<FAR>
      IFDIF <f>,<Far>
       .ERRNB <z> ;; Too many args to .WHILE
       .WHILE <>,<f>,<x>,<y>
       EXITM
      ENDIF
     ENDIF
    ENDIF
   ENDIF
   .ERRB <y> ; Condition or DO expected
   ?PUSH ?CTR,?while
   ?CTR = ?CTR+1
   ?LBL  ?R,%?STKTOP
   IFB <z>   ;; x is condition, y is 'DO'
      .ERRDIF <y>,<DO> ; DO expected
      .ERRB <x> ; No condition for .WHILE
      IFB <f>
         ?J n&x,?B,%?STKTOP
      ELSE
         ?JNF x,?B,%?STKTOP
      ENDIF
   ELSE
      .ERRDIF <z>,<DO>  ; DO expected
?mx x
      IFB <f>
         ?J n&y,?B,%?STKTOP
      ELSE
         ?JNF y,?B,%?STKTOP
      ENDIF
   ENDIF
ENDM

.OD MACRO ;; End of loop.
   IF (?STKTOPT EQ ?forever) OR (?STKTOPT EQ ?while)
      ?J <>,?R,%?STKTOP
      ?LBL  ?B,%?STKTOP
      ?POP
   ELSE
      .ERR  ; Nesting error
   ENDIF
ENDM

.BREAK MACRO f,cc    ;; Break from loop - this may involve searching down stack!
   IFNB <f>
    IFDIF <f>,<far>
     IFDIF <f>,<FAR>
      IFDIF <f>,<Far>
       .ERRNB <cc> ;; Too many args to .BREAK
       .BREAK <>,<f>
       EXITM
      ENDIF
     ENDIF
    ENDIF
   ENDIF
   ?brkcnt=?dbsp
   ?brklab=-1
   .ERRE ?dbsp   ; BREAK outside all loops
   REPT ?dbsp
      ?AVAL ?brklab,?STKT,%?brkcnt
      IF (?brklab EQ ?forever) OR (?brklab EQ ?while) OR (?brklab EQ ?repeat)
         ?AVAL ?brklab,?STK,%?brkcnt
         IFB <f>
            ?J <cc>,?B,%?brklab
         ELSE
            ?JNF n&cc,?B,%?brklab
         ENDIF
         EXITM
      ENDIF
      ?brkcnt = ?brkcnt - 1
      .ERRE ?brkcnt  ; BREAK outside all loops
   ENDM
ENDM
            qlist    ;; Restores listing.
